﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Ckknight.StateMachine
{
    public sealed partial class StateMachineBuilder<TState, TTrigger, TModel>
    {
        /// <summary>
        /// Represents a configurator for a specific state of a <see cref="StateMachineBuilder&lt;TState, TTrigger, TModel&gt;"/>.
        /// </summary>
        public sealed class StateBuilder
        {
            #region Private fields

            private readonly StateMachineBuilder<TState, TTrigger, TModel> _builder;
            private readonly TState _state;
            private readonly Dictionary<TTrigger, KeyValuePair<TState, Func<StateMachine<TState, TTrigger, TModel>.Transition, bool>>> _transitions;
            private readonly Dictionary<TTrigger, KeyValuePair<Func<StateMachine<TState, TTrigger, TModel>.Transition, TState>, Func<StateMachine<TState, TTrigger, TModel>.Transition, bool>>> _dynamicTransitions;
            private readonly List<TTrigger> _ignores;
            private readonly Dictionary<TTrigger, Func<StateMachine<TState, TTrigger, TModel>.Transition, bool>> _reentries;
            private readonly List<Action<StateMachine<TState, TTrigger, TModel>.Transition>> _onEntries;
            private readonly List<Action<StateMachine<TState, TTrigger, TModel>.Transition>> _onExits;
            private readonly Dictionary<TTrigger, List<Action<StateMachine<TState, TTrigger, TModel>.Transition>>> _onEntryFroms;
            private readonly Dictionary<TTrigger, List<Action<StateMachine<TState, TTrigger, TModel>.Transition>>> _onExitTos;
            private bool _hasSuperState;
            private TState _superState;

            #endregion

            #region Internal constructor

            /// <summary>
            /// Instantiate a new <see cref="StateBuilder"/>.
            /// </summary>
            /// <param name="builder">The overarching <see cref="StateMachineBuilder&lt;TState, TTrigger, TModel&gt;"/> this is configuring on.</param>
            /// <param name="state">The state the current <see cref="StateBuilder"/> is configuring.</param>
            internal StateBuilder(StateMachineBuilder<TState, TTrigger, TModel> builder, TState state)
            {
                _builder = builder;
                _state = state;
                _transitions = new Dictionary<TTrigger, KeyValuePair<TState, Func<StateMachine<TState, TTrigger, TModel>.Transition, bool>>>(_builder._triggerComparer);
                _dynamicTransitions = new Dictionary<TTrigger, KeyValuePair<Func<StateMachine<TState, TTrigger, TModel>.Transition, TState>, Func<StateMachine<TState, TTrigger, TModel>.Transition, bool>>>(_builder._triggerComparer);
                _ignores = new List<TTrigger>();
                _reentries = new Dictionary<TTrigger, Func<StateMachine<TState, TTrigger, TModel>.Transition, bool>>();
                _onEntries = new List<Action<StateMachine<TState, TTrigger, TModel>.Transition>>();
                _onExits = new List<Action<StateMachine<TState, TTrigger, TModel>.Transition>>();
                _onEntryFroms = new Dictionary<TTrigger, List<Action<StateMachine<TState, TTrigger, TModel>.Transition>>>(_builder._triggerComparer);
                _onExitTos = new Dictionary<TTrigger, List<Action<StateMachine<TState, TTrigger, TModel>.Transition>>>(_builder._triggerComparer);
                _hasSuperState = false;
                _superState = default(TState);
            }

            #endregion

            #region Internal methods

            private static readonly TState[] _emptyStateArray = new TState[0];
            /// <summary>
            /// Convert the current snapshot of the <see cref="StateBuilder"/> to a <see cref="StateMachine&lt;TState, TTrigger, TModel&gt;.StateData"/>.
            /// </summary>
            /// <returns>A <see cref="StateMachine&lt;TState, TTrigger, TModel&gt;.StateData"/></returns>
            internal StateMachine<TState, TTrigger, TModel>.StateData ToStateData()
            {
                if (_hasSuperState)
                {
                    var transitions = new Dictionary<TTrigger, KeyValuePair<TState, Func<StateMachine<TState, TTrigger, TModel>.Transition, bool>>>(_transitions, _builder._triggerComparer);
                    foreach (var pair in _reentries)
                    {
                        // since the StateData acts as a frozen configuration, we can just merge the reentries onto the normal transitions
                        transitions.Add(pair.Key, new KeyValuePair<TState, Func<StateMachine<TState, TTrigger, TModel>.Transition, bool>>(_state, pair.Value));
                    }
                    var dynamicTransitions = new Dictionary<TTrigger, KeyValuePair<Func<StateMachine<TState, TTrigger, TModel>.Transition, TState>, Func<StateMachine<TState, TTrigger, TModel>.Transition, bool>>>(_dynamicTransitions, _builder._triggerComparer);
                    var ignores = new List<TTrigger>(_ignores);
                    var onEntries = new List<Action<StateMachine<TState, TTrigger, TModel>.Transition>>(_onEntries);
                    var onExits = new List<Action<StateMachine<TState, TTrigger, TModel>.Transition>>(_onExits);
                    var onEntryFroms = _onEntryFroms.ToDictionary(
                        p => p.Key,
                        p => new List<Action<StateMachine<TState, TTrigger, TModel>.Transition>>(p.Value),
                        _builder._triggerComparer);
                    var onExitTos = _onExitTos.ToDictionary(
                        p => p.Key,
                        p => new List<Action<StateMachine<TState, TTrigger, TModel>.Transition>>(p.Value),
                        _builder._triggerComparer);

                    var parents = new List<TState> { _superState };
                    var superConfig = _builder.GetConfiguration(_superState);
                    while (superConfig != null)
                    {
                        foreach (var pair in superConfig._transitions)
                        {
                            if (!transitions.ContainsKey(pair.Key)
                                && !dynamicTransitions.ContainsKey(pair.Key)
                                && !ignores.Contains(pair.Key, _builder._triggerComparer))
                            {
                                transitions.Add(pair.Key, pair.Value);
                            }
                        }
                        foreach (var pair in superConfig._reentries)
                        {
                            if (!transitions.ContainsKey(pair.Key)
                                && !dynamicTransitions.ContainsKey(pair.Key)
                                && !ignores.Contains(pair.Key, _builder._triggerComparer))
                            {
                                transitions.Add(pair.Key, new KeyValuePair<TState, Func<StateMachine<TState, TTrigger, TModel>.Transition, bool>>(_state, pair.Value));
                            }
                        }
                        foreach (var pair in superConfig._dynamicTransitions)
                        {
                            if (!transitions.ContainsKey(pair.Key)
                                && !dynamicTransitions.ContainsKey(pair.Key)
                                && !ignores.Contains(pair.Key, _builder._triggerComparer))
                            {
                                dynamicTransitions.Add(pair.Key, pair.Value);
                            }
                        }
                        foreach (var trigger in superConfig._ignores)
                        {
                            if (!transitions.ContainsKey(trigger)
                                && !dynamicTransitions.ContainsKey(trigger)
                                && !ignores.Contains(trigger, _builder._triggerComparer))
                            {
                                ignores.Add(trigger);
                            }
                        }
                        onEntries.InsertRange(0, superConfig._onEntries);
                        onExits.AddRange(superConfig._onExits);
                        foreach (var pair in superConfig._onEntryFroms)
                        {
                            List<Action<StateMachine<TState, TTrigger, TModel>.Transition>> actions;
                            if (!onEntryFroms.TryGetValue(pair.Key, out actions))
                            {
                                onEntryFroms[pair.Key] = actions = new List<Action<StateMachine<TState, TTrigger, TModel>.Transition>>();
                            }
                            actions.InsertRange(0, pair.Value);
                        }
                        foreach (var pair in superConfig._onExitTos)
                        {
                            List<Action<StateMachine<TState, TTrigger, TModel>.Transition>> actions;
                            if (!onExitTos.TryGetValue(pair.Key, out actions))
                            {
                                onExitTos[pair.Key] = actions = new List<Action<StateMachine<TState, TTrigger, TModel>.Transition>>();
                            }
                            actions.AddRange(pair.Value);
                        }

                        if (!superConfig._hasSuperState)
                        {
                            break;
                        }
                        parents.Add(superConfig._superState);
                        superConfig = _builder.GetConfiguration(superConfig._superState);
                    }

                    return new StateMachine<TState, TTrigger, TModel>.StateData(
                        _state,
                        Freeze(transitions),
                        Freeze(dynamicTransitions),
                        Freeze(ignores),
                        Freeze(onEntries),
                        Freeze(onExits),
                        Freeze(onEntryFroms),
                        Freeze(onExitTos),
                        parents.ToArray());
                }
                else
                {
                    Dictionary<TTrigger, KeyValuePair<TState, Func<StateMachine<TState, TTrigger, TModel>.Transition, bool>>> transitions = _transitions;
                    if (_reentries.Count > 0)
                    {
                        // since the StateData acts as a frozen configuration, we can just merge the reentries onto the normal transitions
                        transitions = new Dictionary<TTrigger, KeyValuePair<TState, Func<StateMachine<TState, TTrigger, TModel>.Transition, bool>>>(transitions);
                        foreach (var pair in _reentries)
                        {
                            transitions.Add(pair.Key, new KeyValuePair<TState, Func<StateMachine<TState, TTrigger, TModel>.Transition, bool>>(_state, pair.Value));
                        }
                    }

                    return new StateMachine<TState, TTrigger, TModel>.StateData(
                        _state,
                        Freeze(transitions),
                        Freeze(_dynamicTransitions),
                        Freeze(_ignores),
                        Freeze(_onEntries),
                        Freeze(_onExits),
                        Freeze(_onEntryFroms),
                        Freeze(_onExitTos),
                        _emptyStateArray);
                }
            }

            #endregion

            #region Public methods

            #region Permit

            /// <summary>
            /// Permit a transition to the provided <paramref name="state"/> when the provided <paramref name="trigger"/> is fired.
            /// </summary>
            /// <param name="trigger">A trigger that may be fired</param>
            /// <param name="state">The state to transition to</param>
            /// <returns>The current <see cref="StateBuilder"/>, allowing for a fluent syntax</returns>
            /// <exception cref="System.ArgumentNullException">
            /// Thrown when <paramref name="trigger"/> or <paramref name="state"/> is null.
            /// </exception>
            /// <exception cref="System.ArgumentException">
            /// Thrown when <paramref name="trigger"/> has already been configured.
            /// </exception>
            public StateBuilder Permit(TTrigger trigger, TState state)
            {
                if (trigger == null)
                {
                    throw new ArgumentNullException("trigger");
                }
                else if (state == null)
                {
                    throw new ArgumentNullException("state");
                }
                else if (IsTriggerConfigured(trigger))
                {
                    throw new ArgumentException(string.Format("Transition already specified for state {0} with trigger {1}", _state, trigger));
                }

                _transitions[trigger] = new KeyValuePair<TState, Func<StateMachine<TState, TTrigger, TModel>.Transition, bool>>(state, null);
                
                return this;
            }

            #endregion

            #region Dynamic Permit

            private StateBuilder InternalPermit(TTrigger trigger, Func<StateMachine<TState, TTrigger, TModel>.Transition, TState> stateChooser)
            {
                if (trigger == null)
                {
                    throw new ArgumentNullException("trigger");
                }
                else if (stateChooser == null)
                {
                    throw new ArgumentNullException("stateChooser");
                }
                else if (IsTriggerConfigured(trigger))
                {
                    throw new ArgumentException(string.Format("Transition already specified for state {0} with trigger {1}", _state, trigger));
                }

                _dynamicTransitions[trigger] = new KeyValuePair<Func<StateMachine<TState, TTrigger, TModel>.Transition, TState>, Func<StateMachine<TState, TTrigger, TModel>.Transition, bool>>(stateChooser, null);
                
                return this;
            }

            /// <summary>
            /// Permit a transition to the return value of the provided <paramref name="stateChooser"/> when the provided <paramref name="trigger"/> is fired.
            /// </summary>
            /// <param name="trigger">A trigger that may be fired</param>
            /// <param name="stateChooser">A function that should return the state to transition to</param>
            /// <returns>The current <see cref="StateBuilder"/>, allowing for a fluent syntax</returns>
            /// <exception cref="System.ArgumentNullException">
            /// Thrown when <paramref name="trigger"/> or <paramref name="stateChooser"/> is null.
            /// </exception>
            /// <exception cref="System.ArgumentException">
            /// Thrown when <paramref name="trigger"/> has already been configured.
            /// </exception>
            public StateBuilder Permit(TTrigger trigger, Func<TState> stateChooser)
            {
                if (stateChooser == null)
                {
                    throw new ArgumentNullException("stateChooser");
                }

                return InternalPermit(trigger, t => stateChooser());
            }

            /// <summary>
            /// Permit a transition to the return value of the provided <paramref name="stateChooser"/> when the provided <paramref name="trigger"/> is fired.
            /// </summary>
            /// <param name="trigger">A trigger that may be fired</param>
            /// <param name="stateChooser">A function that should return the state to transition to</param>
            /// <returns>The current <see cref="StateBuilder"/>, allowing for a fluent syntax</returns>
            /// <exception cref="System.ArgumentNullException">
            /// Thrown when <paramref name="trigger"/> or <paramref name="stateChooser"/> is null.
            /// </exception>
            /// <exception cref="System.ArgumentException">
            /// Thrown when <paramref name="trigger"/> has already been configured.
            /// </exception>
            public StateBuilder Permit(TTrigger trigger, Func<StateMachine<TState, TTrigger, TModel>.Transition, TState> stateChooser)
            {
                if (stateChooser == null)
                {
                    throw new ArgumentNullException("stateChooser");
                }
                _builder.ValidateTriggerParameters(trigger, "stateChooser");

                return InternalPermit(trigger, stateChooser);
            }

            /// <summary>
            /// Permit a transition to the return value of the provided <paramref name="stateChooser"/> when the provided <paramref name="trigger"/> is fired.
            /// </summary>
            /// <param name="trigger">A trigger that may be fired</param>
            /// <param name="stateChooser">A function that should return the state to transition to</param>
            /// <returns>The current <see cref="StateBuilder"/>, allowing for a fluent syntax</returns>
            /// <exception cref="System.ArgumentNullException">
            /// Thrown when <paramref name="trigger"/> or <paramref name="stateChooser"/> is null.
            /// </exception>
            /// <exception cref="System.ArgumentException">
            /// Thrown when <paramref name="trigger"/> has already been configured.
            /// </exception>
            public StateBuilder Permit<TArg1>(TTrigger trigger, Func<StateMachine<TState, TTrigger, TModel>.Transition<TArg1>, TState> stateChooser)
            {
                if (stateChooser == null)
                {
                    throw new ArgumentNullException("stateChooser");
                }
                _builder.ValidateTriggerParameters<TArg1>(trigger, "stateChooser");

                return InternalPermit(trigger, t => stateChooser((StateMachine<TState, TTrigger, TModel>.Transition<TArg1>)t));
            }

            /// <summary>
            /// Permit a transition to the return value of the provided <paramref name="stateChooser"/> when the provided <paramref name="trigger"/> is fired.
            /// </summary>
            /// <param name="trigger">A trigger that may be fired</param>
            /// <param name="stateChooser">A function that should return the state to transition to</param>
            /// <returns>The current <see cref="StateBuilder"/>, allowing for a fluent syntax</returns>
            /// <exception cref="System.ArgumentNullException">
            /// Thrown when <paramref name="trigger"/> or <paramref name="stateChooser"/> is null.
            /// </exception>
            /// <exception cref="System.ArgumentException">
            /// Thrown when <paramref name="trigger"/> has already been configured.
            /// </exception>
            public StateBuilder Permit<TArg1, TArg2>(TTrigger trigger, Func<StateMachine<TState, TTrigger, TModel>.Transition<TArg1, TArg2>, TState> stateChooser)
            {
                if (stateChooser == null)
                {
                    throw new ArgumentNullException("stateChooser");
                }
                _builder.ValidateTriggerParameters<TArg1, TArg2>(trigger, "stateChooser");

                return InternalPermit(trigger, t => stateChooser((StateMachine<TState, TTrigger, TModel>.Transition<TArg1, TArg2>)t));
            }

            /// <summary>
            /// Permit a transition to the return value of the provided <paramref name="stateChooser"/> when the provided <paramref name="trigger"/> is fired.
            /// </summary>
            /// <param name="trigger">A trigger that may be fired</param>
            /// <param name="stateChooser">A function that should return the state to transition to</param>
            /// <returns>The current <see cref="StateBuilder"/>, allowing for a fluent syntax</returns>
            /// <exception cref="System.ArgumentNullException">
            /// Thrown when <paramref name="trigger"/> or <paramref name="stateChooser"/> is null.
            /// </exception>
            /// <exception cref="System.ArgumentException">
            /// Thrown when <paramref name="trigger"/> has already been configured.
            /// </exception>
            public StateBuilder Permit<TArg1, TArg2, TArg3>(TTrigger trigger, Func<StateMachine<TState, TTrigger, TModel>.Transition<TArg1, TArg2, TArg3>, TState> stateChooser)
            {
                if (stateChooser == null)
                {
                    throw new ArgumentNullException("stateChooser");
                }
                _builder.ValidateTriggerParameters<TArg1, TArg2, TArg3>(trigger, "stateChooser");

                return InternalPermit(trigger, t => stateChooser((StateMachine<TState, TTrigger, TModel>.Transition<TArg1, TArg2, TArg3>)t));
            }

            /// <summary>
            /// Permit a transition to the return value of the provided <paramref name="stateChooser"/> when the provided <paramref name="trigger"/> is fired.
            /// </summary>
            /// <param name="trigger">A trigger that may be fired</param>
            /// <param name="stateChooser">A function that should return the state to transition to</param>
            /// <returns>The current <see cref="StateBuilder"/>, allowing for a fluent syntax</returns>
            /// <exception cref="System.ArgumentNullException">
            /// Thrown when <paramref name="trigger"/> or <paramref name="stateChooser"/> is null.
            /// </exception>
            /// <exception cref="System.ArgumentException">
            /// Thrown when <paramref name="trigger"/> has already been configured.
            /// </exception>
            public StateBuilder Permit<TArg1, TArg2, TArg3, TArg4>(TTrigger trigger, Func<StateMachine<TState, TTrigger, TModel>.Transition<TArg1, TArg2, TArg3, TArg4>, TState> stateChooser)
            {
                if (stateChooser == null)
                {
                    throw new ArgumentNullException("stateChooser");
                }
                _builder.ValidateTriggerParameters<TArg1, TArg2, TArg3, TArg4>(trigger, "stateChooser");

                return InternalPermit(trigger, t => stateChooser((StateMachine<TState, TTrigger, TModel>.Transition<TArg1, TArg2, TArg3, TArg4>)t));
            }

            #endregion

            #region PermitIf

            private StateBuilder InternalPermitIf(TTrigger trigger, TState state, Func<StateMachine<TState, TTrigger, TModel>.Transition, bool> guard)
            {
                if (trigger == null)
                {
                    throw new ArgumentNullException("trigger");
                }
                else if (state == null)
                {
                    throw new ArgumentNullException("state");
                }
                else if (IsTriggerConfigured(trigger))
                {
                    throw new ArgumentException(string.Format("Transition already specified for state {0} with trigger {1}", _state, trigger));
                }

                _transitions[trigger] = new KeyValuePair<TState, Func<StateMachine<TState, TTrigger, TModel>.Transition, bool>>(state, guard);
                
                return this;
            }

            /// <summary>
            /// Permit a transition to the provided <paramref name="state"/> when the provided <paramref name="trigger"/> is fired.
            /// </summary>
            /// <param name="trigger">A trigger that may be fired</param>
            /// <param name="state">The state to transition to</param>
            /// <param name="guard">A function that will permit the transition if true is returned and ignore it otherwise</param>
            /// <returns>The current <see cref="StateBuilder"/>, allowing for a fluent syntax</returns>
            /// <exception cref="System.ArgumentNullException">
            /// Thrown when <paramref name="trigger"/> or <paramref name="state"/> is null.
            /// </exception>
            /// <exception cref="System.ArgumentException">
            /// Thrown when <paramref name="trigger"/> has already been configured.
            /// </exception>
            public StateBuilder PermitIf(TTrigger trigger, TState state, Func<bool> guard)
            {
                if (guard == null)
                {
                    throw new ArgumentNullException("guard");
                }
                return InternalPermitIf(trigger, state, t => guard());
            }

            /// <summary>
            /// Permit a transition to the provided <paramref name="state"/> when the provided <paramref name="trigger"/> is fired.
            /// </summary>
            /// <param name="trigger">A trigger that may be fired</param>
            /// <param name="state">The state to transition to</param>
            /// <param name="guard">A function that will permit the transition if true is returned and ignore it otherwise</param>
            /// <returns>The current <see cref="StateBuilder"/>, allowing for a fluent syntax</returns>
            /// <exception cref="System.ArgumentNullException">
            /// Thrown when <paramref name="trigger"/> or <paramref name="state"/> is null.
            /// </exception>
            /// <exception cref="System.ArgumentException">
            /// Thrown when <paramref name="trigger"/> has already been configured.
            /// </exception>
            public StateBuilder PermitIf(TTrigger trigger, TState state, Func<StateMachine<TState, TTrigger, TModel>.Transition, bool> guard)
            {
                if (guard == null)
                {
                    throw new ArgumentNullException("guard");
                }
                _builder.ValidateTriggerParameters(trigger, "guard");
                return InternalPermitIf(trigger, state, guard);
            }

            /// <summary>
            /// Permit a transition to the provided <paramref name="state"/> when the provided <paramref name="trigger"/> is fired.
            /// </summary>
            /// <param name="trigger">A trigger that may be fired</param>
            /// <param name="state">The state to transition to</param>
            /// <param name="guard">A function that will permit the transition if true is returned and ignore it otherwise</param>
            /// <returns>The current <see cref="StateBuilder"/>, allowing for a fluent syntax</returns>
            /// <exception cref="System.ArgumentNullException">
            /// Thrown when <paramref name="trigger"/> or <paramref name="state"/> is null.
            /// </exception>
            /// <exception cref="System.ArgumentException">
            /// Thrown when <paramref name="trigger"/> has already been configured.
            /// </exception>
            public StateBuilder PermitIf<TArg1>(TTrigger trigger, TState state, Func<StateMachine<TState, TTrigger, TModel>.Transition<TArg1>, bool> guard)
            {
                if (guard == null)
                {
                    throw new ArgumentNullException("guard");
                }
                _builder.ValidateTriggerParameters<TArg1>(trigger, "guard");
                return InternalPermitIf(trigger, state, t => guard((StateMachine<TState, TTrigger, TModel>.Transition<TArg1>)t));
            }

            /// <summary>
            /// Permit a transition to the provided <paramref name="state"/> when the provided <paramref name="trigger"/> is fired.
            /// </summary>
            /// <param name="trigger">A trigger that may be fired</param>
            /// <param name="state">The state to transition to</param>
            /// <param name="guard">A function that will permit the transition if true is returned and ignore it otherwise</param>
            /// <returns>The current <see cref="StateBuilder"/>, allowing for a fluent syntax</returns>
            /// <exception cref="System.ArgumentNullException">
            /// Thrown when <paramref name="trigger"/> or <paramref name="state"/> is null.
            /// </exception>
            /// <exception cref="System.ArgumentException">
            /// Thrown when <paramref name="trigger"/> has already been configured.
            /// </exception>
            public StateBuilder PermitIf<TArg1, TArg2>(TTrigger trigger, TState state, Func<StateMachine<TState, TTrigger, TModel>.Transition<TArg1, TArg2>, bool> guard)
            {
                if (guard == null)
                {
                    throw new ArgumentNullException("guard");
                }
                _builder.ValidateTriggerParameters<TArg1, TArg2>(trigger, "guard");
                return InternalPermitIf(trigger, state, t => guard((StateMachine<TState, TTrigger, TModel>.Transition<TArg1, TArg2>)t));
            }

            /// <summary>
            /// Permit a transition to the provided <paramref name="state"/> when the provided <paramref name="trigger"/> is fired.
            /// </summary>
            /// <param name="trigger">A trigger that may be fired</param>
            /// <param name="state">The state to transition to</param>
            /// <param name="guard">A function that will permit the transition if true is returned and ignore it otherwise</param>
            /// <returns>The current <see cref="StateBuilder"/>, allowing for a fluent syntax</returns>
            /// <exception cref="System.ArgumentNullException">
            /// Thrown when <paramref name="trigger"/> or <paramref name="state"/> is null.
            /// </exception>
            /// <exception cref="System.ArgumentException">
            /// Thrown when <paramref name="trigger"/> has already been configured.
            /// </exception>
            public StateBuilder PermitIf<TArg1, TArg2, TArg3>(TTrigger trigger, TState state, Func<StateMachine<TState, TTrigger, TModel>.Transition<TArg1, TArg2, TArg3>, bool> guard)
            {
                if (guard == null)
                {
                    throw new ArgumentNullException("guard");
                }
                _builder.ValidateTriggerParameters<TArg1, TArg2, TArg3>(trigger, "guard");
                return InternalPermitIf(trigger, state, t => guard((StateMachine<TState, TTrigger, TModel>.Transition<TArg1, TArg2, TArg3>)t));
            }

            /// <summary>
            /// Permit a transition to the provided <paramref name="state"/> when the provided <paramref name="trigger"/> is fired.
            /// </summary>
            /// <param name="trigger">A trigger that may be fired</param>
            /// <param name="state">The state to transition to</param>
            /// <param name="guard">A function that will permit the transition if true is returned and ignore it otherwise</param>
            /// <returns>The current <see cref="StateBuilder"/>, allowing for a fluent syntax</returns>
            /// <exception cref="System.ArgumentNullException">
            /// Thrown when <paramref name="trigger"/> or <paramref name="state"/> is null.
            /// </exception>
            /// <exception cref="System.ArgumentException">
            /// Thrown when <paramref name="trigger"/> has already been configured.
            /// </exception>
            public StateBuilder PermitIf<TArg1, TArg2, TArg3, TArg4>(TTrigger trigger, TState state, Func<StateMachine<TState, TTrigger, TModel>.Transition<TArg1, TArg2, TArg3, TArg4>, bool> guard)
            {
                if (guard == null)
                {
                    throw new ArgumentNullException("guard");
                }
                _builder.ValidateTriggerParameters<TArg1, TArg2, TArg3, TArg4>(trigger, "guard");
                return InternalPermitIf(trigger, state, t => guard((StateMachine<TState, TTrigger, TModel>.Transition<TArg1, TArg2, TArg3, TArg4>)t));
            }

            #endregion

            #region Dynamic PermitIf

            private StateBuilder InternalPermitIf(TTrigger trigger, Func<StateMachine<TState, TTrigger, TModel>.Transition, TState> stateChooser, Func<StateMachine<TState, TTrigger, TModel>.Transition, bool> guard)
            {
                if (trigger == null)
                {
                    throw new ArgumentNullException("trigger");
                }
                else if (stateChooser == null)
                {
                    throw new ArgumentNullException("stateChooser");
                }
                else if (guard == null)
                {
                    throw new ArgumentNullException("guard");
                }
                else if (IsTriggerConfigured(trigger))
                {
                    throw new ArgumentException(string.Format("Transition already specified for state {0} with trigger {1}", _state, trigger));
                }

                _dynamicTransitions[trigger] = new KeyValuePair<Func<StateMachine<TState, TTrigger, TModel>.Transition, TState>, Func<StateMachine<TState, TTrigger, TModel>.Transition, bool>>(stateChooser, guard);
                
                return this;
            }

            /// <summary>
            /// Permit a transition to the return value of the provided <paramref name="stateChooser"/> when the provided <paramref name="trigger"/> is fired.
            /// </summary>
            /// <param name="trigger">A trigger that may be fired</param>
            /// <param name="stateChooser">A function that should return the state to transition to</param>
            /// <param name="guard">A function that will permit the transition if true is returned and ignore it otherwise</param>
            /// <returns>The current <see cref="StateBuilder"/>, allowing for a fluent syntax</returns>
            /// <exception cref="System.ArgumentNullException">
            /// Thrown when <paramref name="trigger"/> or <paramref name="stateChooser"/> is null.
            /// </exception>
            /// <exception cref="System.ArgumentException">
            /// Thrown when <paramref name="trigger"/> has already been configured.
            /// </exception>
            public StateBuilder PermitIf(TTrigger trigger, Func<TState> stateChooser, Func<bool> guard)
            {
                if (stateChooser == null)
                {
                    throw new ArgumentNullException("stateChooser");
                }
                else if (guard == null)
                {
                    throw new ArgumentNullException("guard");
                }

                return InternalPermitIf(trigger, t => stateChooser(), t => guard());
            }

            /// <summary>
            /// Permit a transition to the return value of the provided <paramref name="stateChooser"/> when the provided <paramref name="trigger"/> is fired.
            /// </summary>
            /// <param name="trigger">A trigger that may be fired</param>
            /// <param name="stateChooser">A function that should return the state to transition to</param>
            /// <param name="guard">A function that will permit the transition if true is returned and ignore it otherwise</param>
            /// <returns>The current <see cref="StateBuilder"/>, allowing for a fluent syntax</returns>
            /// <exception cref="System.ArgumentNullException">
            /// Thrown when <paramref name="trigger"/> or <paramref name="stateChooser"/> is null.
            /// </exception>
            /// <exception cref="System.ArgumentException">
            /// Thrown when <paramref name="trigger"/> has already been configured.
            /// </exception>
            public StateBuilder PermitIf(TTrigger trigger, Func<StateMachine<TState, TTrigger, TModel>.Transition, TState> stateChooser, Func<StateMachine<TState, TTrigger, TModel>.Transition, bool> guard)
            {
                if (stateChooser == null)
                {
                    throw new ArgumentNullException("stateChooser");
                }
                else if (guard == null)
                {
                    throw new ArgumentNullException("guard");
                }
                
                _builder.ValidateTriggerParameters(trigger, "stateChooser");

                return InternalPermitIf(trigger, stateChooser, guard);
            }

            /// <summary>
            /// Permit a transition to the return value of the provided <paramref name="stateChooser"/> when the provided <paramref name="trigger"/> is fired.
            /// </summary>
            /// <param name="trigger">A trigger that may be fired</param>
            /// <param name="stateChooser">A function that should return the state to transition to</param>
            /// <param name="guard">A function that will permit the transition if true is returned and ignore it otherwise</param>
            /// <returns>The current <see cref="StateBuilder"/>, allowing for a fluent syntax</returns>
            /// <exception cref="System.ArgumentNullException">
            /// Thrown when <paramref name="trigger"/> or <paramref name="stateChooser"/> is null.
            /// </exception>
            /// <exception cref="System.ArgumentException">
            /// Thrown when <paramref name="trigger"/> has already been configured.
            /// </exception>
            public StateBuilder PermitIf<TArg1>(TTrigger trigger, Func<StateMachine<TState, TTrigger, TModel>.Transition<TArg1>, TState> stateChooser, Func<StateMachine<TState, TTrigger, TModel>.Transition<TArg1>, bool> guard)
            {
                if (stateChooser == null)
                {
                    throw new ArgumentNullException("stateChooser");
                }
                else if (guard == null)
                {
                    throw new ArgumentNullException("guard");
                }

                _builder.ValidateTriggerParameters<TArg1>(trigger, "stateChooser");

                return InternalPermitIf(trigger, t => stateChooser((StateMachine<TState, TTrigger, TModel>.Transition<TArg1>)t), t => guard((StateMachine<TState, TTrigger, TModel>.Transition<TArg1>)t));
            }

            /// <summary>
            /// Permit a transition to the return value of the provided <paramref name="stateChooser"/> when the provided <paramref name="trigger"/> is fired.
            /// </summary>
            /// <param name="trigger">A trigger that may be fired</param>
            /// <param name="stateChooser">A function that should return the state to transition to</param>
            /// <param name="guard">A function that will permit the transition if true is returned and ignore it otherwise</param>
            /// <returns>The current <see cref="StateBuilder"/>, allowing for a fluent syntax</returns>
            /// <exception cref="System.ArgumentNullException">
            /// Thrown when <paramref name="trigger"/> or <paramref name="stateChooser"/> is null.
            /// </exception>
            /// <exception cref="System.ArgumentException">
            /// Thrown when <paramref name="trigger"/> has already been configured.
            /// </exception>
            public StateBuilder PermitIf<TArg1, TArg2>(TTrigger trigger, Func<StateMachine<TState, TTrigger, TModel>.Transition<TArg1, TArg2>, TState> stateChooser, Func<StateMachine<TState, TTrigger, TModel>.Transition<TArg1, TArg2>, bool> guard)
            {
                if (stateChooser == null)
                {
                    throw new ArgumentNullException("stateChooser");
                }
                else if (guard == null)
                {
                    throw new ArgumentNullException("guard");
                }

                _builder.ValidateTriggerParameters<TArg1, TArg2>(trigger, "stateChooser");

                return InternalPermitIf(trigger, t => stateChooser((StateMachine<TState, TTrigger, TModel>.Transition<TArg1, TArg2>)t), t => guard((StateMachine<TState, TTrigger, TModel>.Transition<TArg1, TArg2>)t));
            }

            /// <summary>
            /// Permit a transition to the return value of the provided <paramref name="stateChooser"/> when the provided <paramref name="trigger"/> is fired.
            /// </summary>
            /// <param name="trigger">A trigger that may be fired</param>
            /// <param name="stateChooser">A function that should return the state to transition to</param>
            /// <param name="guard">A function that will permit the transition if true is returned and ignore it otherwise</param>
            /// <returns>The current <see cref="StateBuilder"/>, allowing for a fluent syntax</returns>
            /// <exception cref="System.ArgumentNullException">
            /// Thrown when <paramref name="trigger"/> or <paramref name="stateChooser"/> is null.
            /// </exception>
            /// <exception cref="System.ArgumentException">
            /// Thrown when <paramref name="trigger"/> has already been configured.
            /// </exception>
            public StateBuilder PermitIf<TArg1, TArg2, TArg3>(TTrigger trigger, Func<StateMachine<TState, TTrigger, TModel>.Transition<TArg1, TArg2, TArg3>, TState> stateChooser, Func<StateMachine<TState, TTrigger, TModel>.Transition<TArg1, TArg2, TArg3>, bool> guard)
            {
                if (stateChooser == null)
                {
                    throw new ArgumentNullException("stateChooser");
                }
                else if (guard == null)
                {
                    throw new ArgumentNullException("guard");
                }

                _builder.ValidateTriggerParameters<TArg1, TArg2, TArg3>(trigger, "stateChooser");

                return InternalPermitIf(trigger, t => stateChooser((StateMachine<TState, TTrigger, TModel>.Transition<TArg1, TArg2, TArg3>)t), t => guard((StateMachine<TState, TTrigger, TModel>.Transition<TArg1, TArg2, TArg3>)t));
            }

            /// <summary>
            /// Permit a transition to the return value of the provided <paramref name="stateChooser"/> when the provided <paramref name="trigger"/> is fired.
            /// </summary>
            /// <param name="trigger">A trigger that may be fired</param>
            /// <param name="stateChooser">A function that should return the state to transition to</param>
            /// <param name="guard">A function that will permit the transition if true is returned and ignore it otherwise</param>
            /// <returns>The current <see cref="StateBuilder"/>, allowing for a fluent syntax</returns>
            /// <exception cref="System.ArgumentNullException">
            /// Thrown when <paramref name="trigger"/> or <paramref name="stateChooser"/> is null.
            /// </exception>
            /// <exception cref="System.ArgumentException">
            /// Thrown when <paramref name="trigger"/> has already been configured.
            /// </exception>
            public StateBuilder PermitIf<TArg1, TArg2, TArg3, TArg4>(TTrigger trigger, Func<StateMachine<TState, TTrigger, TModel>.Transition<TArg1, TArg2, TArg3, TArg4>, TState> stateChooser, Func<StateMachine<TState, TTrigger, TModel>.Transition<TArg1, TArg2, TArg3, TArg4>, bool> guard)
            {
                if (stateChooser == null)
                {
                    throw new ArgumentNullException("stateChooser");
                }
                else if (guard == null)
                {
                    throw new ArgumentNullException("guard");
                }

                _builder.ValidateTriggerParameters<TArg1, TArg2, TArg3, TArg4>(trigger, "stateChooser");

                return InternalPermitIf(trigger, t => stateChooser((StateMachine<TState, TTrigger, TModel>.Transition<TArg1, TArg2, TArg3, TArg4>)t), t => guard((StateMachine<TState, TTrigger, TModel>.Transition<TArg1, TArg2, TArg3, TArg4>)t));
            }

            #endregion

            #region PermitReentry

            /// <summary>
            /// Permit a transition to current state when the provided <paramref name="trigger"/> is fired.
            /// </summary>
            /// <remarks>
            /// If a super-state calls this and the re-entry occurs, it will re-enter its own state this parent state.
            /// </remarks>
            /// <param name="trigger">A trigger that may be fired</param>
            /// <returns>The current <see cref="StateBuilder"/>, allowing for a fluent syntax</returns>
            /// <exception cref="System.ArgumentNullException">
            /// Thrown when <paramref name="trigger"/> is null.
            /// </exception>
            /// <exception cref="System.ArgumentException">
            /// Thrown when <paramref name="trigger"/> has already been configured.
            /// </exception>
            public StateBuilder PermitReentry(TTrigger trigger)
            {
                if (trigger == null)
                {
                    throw new ArgumentNullException("trigger");
                }
                else if (IsTriggerConfigured(trigger))
                {
                    throw new ArgumentException(string.Format("Transition already specified for state {0} with trigger {1}", _state, trigger));
                }

                _reentries.Add(trigger, null);
                
                return this;
            }

            #endregion

            #region PermitReentryIf

            private StateBuilder InternalPermitReentryIf(TTrigger trigger, Func<StateMachine<TState, TTrigger, TModel>.Transition, bool> guard)
            {
                if (trigger == null)
                {
                    throw new ArgumentNullException("trigger");
                }
                else if (guard == null)
                {
                    throw new ArgumentNullException("guard");
                }
                else if (IsTriggerConfigured(trigger))
                {
                    throw new ArgumentException(string.Format("Transition already specified for state {0} with trigger {1}", _state, trigger));
                }

                _reentries.Add(trigger, guard);
                
                return this;
            }

            /// <summary>
            /// Permit a transition to current state when the provided <paramref name="trigger"/> is fired if the <paramref name="guard"/> returns true, ignored if it returns false.
            /// </summary>
            /// <remarks>
            /// If a super-state calls this and the re-entry occurs, it will re-enter its own state this parent state.
            /// </remarks>
            /// <param name="trigger">A trigger that may be fired</param>
            /// <param name="guard">A guard clause that will cause the trigger to figure if the return value is true</param>
            /// <returns>The current <see cref="StateBuilder"/>, allowing for a fluent syntax</returns>
            /// <exception cref="System.ArgumentNullException">
            /// Thrown when <paramref name="trigger"/> is null.
            /// </exception>
            /// <exception cref="System.ArgumentException">
            /// Thrown when <paramref name="trigger"/> has already been configured.
            /// </exception>
            public StateBuilder PermitReentryIf(TTrigger trigger, Func<bool> guard)
            {
                if (guard == null)
                {
                    throw new ArgumentNullException("guard");
                }

                return InternalPermitReentryIf(trigger, t => guard());
            }

            /// <summary>
            /// Permit a transition to current state when the provided <paramref name="trigger"/> is fired if the <paramref name="guard"/> returns true, ignored if it returns false.
            /// </summary>
            /// <remarks>
            /// If a super-state calls this and the re-entry occurs, it will re-enter its own state this parent state.
            /// </remarks>
            /// <param name="trigger">A trigger that may be fired</param>
            /// <param name="guard">A guard clause that will cause the trigger to figure if the return value is true</param>
            /// <returns>The current <see cref="StateBuilder"/>, allowing for a fluent syntax</returns>
            /// <exception cref="System.ArgumentNullException">
            /// Thrown when <paramref name="trigger"/> is null.
            /// </exception>
            /// <exception cref="System.ArgumentException">
            /// Thrown when <paramref name="trigger"/> has already been configured.
            /// </exception>
            public StateBuilder PermitReentryIf(TTrigger trigger, Func<StateMachine<TState, TTrigger, TModel>.Transition, bool> guard)
            {
                if (guard == null)
                {
                    throw new ArgumentNullException("guard");
                }

                _builder.ValidateTriggerParameters(trigger, "guard");
                return InternalPermitReentryIf(trigger, guard);
            }

            /// <summary>
            /// Permit a transition to current state when the provided <paramref name="trigger"/> is fired if the <paramref name="guard"/> returns true, ignored if it returns false.
            /// </summary>
            /// <remarks>
            /// If a super-state calls this and the re-entry occurs, it will re-enter its own state this parent state.
            /// </remarks>
            /// <param name="trigger">A trigger that may be fired</param>
            /// <param name="guard">A guard clause that will cause the trigger to figure if the return value is true</param>
            /// <returns>The current <see cref="StateBuilder"/>, allowing for a fluent syntax</returns>
            /// <exception cref="System.ArgumentNullException">
            /// Thrown when <paramref name="trigger"/> is null.
            /// </exception>
            /// <exception cref="System.ArgumentException">
            /// Thrown when <paramref name="trigger"/> has already been configured.
            /// </exception>
            public StateBuilder PermitReentryIf<TArg1>(TTrigger trigger, Func<StateMachine<TState, TTrigger, TModel>.Transition<TArg1>, bool> guard)
            {
                if (guard == null)
                {
                    throw new ArgumentNullException("guard");
                }

                _builder.ValidateTriggerParameters(trigger, "guard");
                return InternalPermitReentryIf(trigger, t => guard((StateMachine<TState, TTrigger, TModel>.Transition<TArg1>)t));
            }

            /// <summary>
            /// Permit a transition to current state when the provided <paramref name="trigger"/> is fired if the <paramref name="guard"/> returns true, ignored if it returns false.
            /// </summary>
            /// <remarks>
            /// If a super-state calls this and the re-entry occurs, it will re-enter its own state this parent state.
            /// </remarks>
            /// <param name="trigger">A trigger that may be fired</param>
            /// <param name="guard">A guard clause that will cause the trigger to figure if the return value is true</param>
            /// <returns>The current <see cref="StateBuilder"/>, allowing for a fluent syntax</returns>
            /// <exception cref="System.ArgumentNullException">
            /// Thrown when <paramref name="trigger"/> is null.
            /// </exception>
            /// <exception cref="System.ArgumentException">
            /// Thrown when <paramref name="trigger"/> has already been configured.
            /// </exception>
            public StateBuilder PermitReentryIf<TArg1, TArg2>(TTrigger trigger, Func<StateMachine<TState, TTrigger, TModel>.Transition<TArg1, TArg2>, bool> guard)
            {
                if (guard == null)
                {
                    throw new ArgumentNullException("guard");
                }

                _builder.ValidateTriggerParameters(trigger, "guard");
                return InternalPermitReentryIf(trigger, t => guard((StateMachine<TState, TTrigger, TModel>.Transition<TArg1, TArg2>)t));
            }

            /// <summary>
            /// Permit a transition to current state when the provided <paramref name="trigger"/> is fired if the <paramref name="guard"/> returns true, ignored if it returns false.
            /// </summary>
            /// <remarks>
            /// If a super-state calls this and the re-entry occurs, it will re-enter its own state this parent state.
            /// </remarks>
            /// <param name="trigger">A trigger that may be fired</param>
            /// <param name="guard">A guard clause that will cause the trigger to figure if the return value is true</param>
            /// <returns>The current <see cref="StateBuilder"/>, allowing for a fluent syntax</returns>
            /// <exception cref="System.ArgumentNullException">
            /// Thrown when <paramref name="trigger"/> is null.
            /// </exception>
            /// <exception cref="System.ArgumentException">
            /// Thrown when <paramref name="trigger"/> has already been configured.
            /// </exception>
            public StateBuilder PermitReentryIf<TArg1, TArg2, TArg3>(TTrigger trigger, Func<StateMachine<TState, TTrigger, TModel>.Transition<TArg1, TArg2, TArg3>, bool> guard)
            {
                if (guard == null)
                {
                    throw new ArgumentNullException("guard");
                }

                _builder.ValidateTriggerParameters(trigger, "guard");
                return InternalPermitReentryIf(trigger, t => guard((StateMachine<TState, TTrigger, TModel>.Transition<TArg1, TArg2, TArg3>)t));
            }

            /// <summary>
            /// Permit a transition to current state when the provided <paramref name="trigger"/> is fired if the <paramref name="guard"/> returns true, ignored if it returns false.
            /// </summary>
            /// <remarks>
            /// If a super-state calls this and the re-entry occurs, it will re-enter its own state this parent state.
            /// </remarks>
            /// <param name="trigger">A trigger that may be fired</param>
            /// <param name="guard">A guard clause that will cause the trigger to figure if the return value is true</param>
            /// <returns>The current <see cref="StateBuilder"/>, allowing for a fluent syntax</returns>
            /// <exception cref="System.ArgumentNullException">
            /// Thrown when <paramref name="trigger"/> is null.
            /// </exception>
            /// <exception cref="System.ArgumentException">
            /// Thrown when <paramref name="trigger"/> has already been configured.
            /// </exception>
            public StateBuilder PermitReentryIf<TArg1, TArg2, TArg3, TArg4>(TTrigger trigger, Func<StateMachine<TState, TTrigger, TModel>.Transition<TArg1, TArg2, TArg3, TArg4>, bool> guard)
            {
                if (guard == null)
                {
                    throw new ArgumentNullException("guard");
                }

                _builder.ValidateTriggerParameters(trigger, "guard");
                return InternalPermitReentryIf(trigger, t => guard((StateMachine<TState, TTrigger, TModel>.Transition<TArg1, TArg2, TArg3, TArg4>)t));
            }

            #endregion

            #region Ignore

            /// <summary>
            /// Ignore the provided <paramref name="trigger"/> if it is fired.
            /// </summary>
            /// <param name="trigger">A trigger that may be fired</param>
            /// <returns>The current <see cref="StateBuilder"/>, allowing for a fluent syntax</returns>
            /// <exception cref="System.ArgumentNullException">
            /// Thrown when <paramref name="trigger"/> is null.
            /// </exception>
            /// <exception cref="System.ArgumentException">
            /// Thrown when <paramref name="trigger"/> has already been configured.
            /// </exception>
            public StateBuilder Ignore(TTrigger trigger)
            {
                if (trigger == null)
                {
                    throw new ArgumentNullException("trigger");
                }
                if (IsTriggerConfigured(trigger))
                {
                    throw new ArgumentException(string.Format("Transition already specified for state {0} with trigger {1}", _state, trigger));
                }

                _ignores.Add(trigger);
                
                return this;
            }

            #endregion

            /// <summary>
            /// Specify that a given <paramref name="action"/> is to be invoked when the current state is entered by any trigger.
            /// </summary>
            /// <remarks>This can be called multiple times.</remarks>
            /// <param name="action">The action to invoke</param>
            /// <returns>The current <see cref="StateBuilder"/>, allowing for a fluent syntax</returns>
            /// <exception cref="System.ArgumentNullException">
            /// Thrown when <paramref name="action"/> is null.
            /// </exception>
            public StateBuilder OnEntry(Action action)
            {
                if (action == null)
                {
                    throw new ArgumentNullException("action");
                }

                return OnEntry(x => action());
            }

            /// <summary>
            /// Specify that a given <paramref name="action"/> is to be invoked when the current state is entered by any trigger.
            /// </summary>
            /// <remarks>This can be called multiple times.</remarks>
            /// <param name="action">The action to invoke</param>
            /// <returns>The current <see cref="StateBuilder"/>, allowing for a fluent syntax</returns>
            /// <exception cref="System.ArgumentNullException">
            /// Thrown when <paramref name="action"/> is null.
            /// </exception>
            public StateBuilder OnEntry(Action<StateMachine<TState, TTrigger, TModel>.Transition> action)
            {
                if (action == null)
                {
                    throw new ArgumentNullException("action");
                }

                _onEntries.Add(action);
                
                return this;
            }

            /// <summary>
            /// Specify that a given <paramref name="action"/> is to be invoked when the current state is entered by the provided <paramref name="trigger"/>.
            /// </summary>
            /// <remarks>This can be called multiple times.</remarks>
            /// <param name="trigger">The trigger that must be fired to invoke the <paramref name="action"/></param>
            /// <param name="action">The action to invoke</param>
            /// <returns>The current <see cref="StateBuilder"/>, allowing for a fluent syntax</returns>
            /// <exception cref="System.ArgumentNullException">
            /// Thrown when <paramref name="trigger"/> or <paramref name="action"/> is null.
            /// </exception>
            public StateBuilder OnEntryFrom(TTrigger trigger, Action action)
            {
                if (action == null)
                {
                    throw new ArgumentNullException("action");
                }

                return OnEntryFrom(trigger, x => action());
            }

            /// <summary>
            /// Specify that a given <paramref name="action"/> is to be invoked when the current state is entered by the provided <paramref name="trigger"/>.
            /// </summary>
            /// <remarks>This can be called multiple times.</remarks>
            /// <param name="trigger">The trigger that must be fired to invoke the <paramref name="action"/></param>
            /// <param name="action">The action to invoke</param>
            /// <returns>The current <see cref="StateBuilder"/>, allowing for a fluent syntax</returns>
            /// <exception cref="System.ArgumentNullException">
            /// Thrown when <paramref name="trigger"/> or <paramref name="action"/> is null.
            /// </exception>
            public StateBuilder OnEntryFrom(TTrigger trigger, Action<StateMachine<TState, TTrigger, TModel>.Transition> action)
            {
                if (trigger == null)
                {
                    throw new ArgumentNullException("trigger");
                }
                else if (action == null)
                {
                    throw new ArgumentNullException("action");
                }

                List<Action<StateMachine<TState, TTrigger, TModel>.Transition>> list;
                if (!_onEntryFroms.TryGetValue(trigger, out list))
                {
                    _onEntryFroms[trigger] = list = new List<Action<StateMachine<TState, TTrigger, TModel>.Transition>>();
                }
                list.Add(action);
                
                return this;
            }

            /// <summary>
            /// Specify that a given <paramref name="action"/> is to be invoked when the current state is exited by any trigger.
            /// </summary>
            /// <remarks>This can be called multiple times.</remarks>
            /// <param name="action">The action to invoke</param>
            /// <returns>The current <see cref="StateBuilder"/>, allowing for a fluent syntax</returns>
            /// <exception cref="System.ArgumentNullException">
            /// Thrown when <paramref name="action"/> is null.
            /// </exception>
            public StateBuilder OnExit(Action action)
            {
                if (action == null)
                {
                    throw new ArgumentNullException("action");
                }

                return OnExit(x => action());
            }

            /// <summary>
            /// Specify that a given <paramref name="action"/> is to be invoked when the current state is exited by any trigger.
            /// </summary>
            /// <remarks>This can be called multiple times.</remarks>
            /// <param name="action">The action to invoke</param>
            /// <returns>The current <see cref="StateBuilder"/>, allowing for a fluent syntax</returns>
            /// <exception cref="System.ArgumentNullException">
            /// Thrown when <paramref name="action"/> is null.
            /// </exception>
            public StateBuilder OnExit(Action<StateMachine<TState, TTrigger, TModel>.Transition> action)
            {
                if (action == null)
                {
                    throw new ArgumentNullException("action");
                }

                _onExits.Add(action);
                
                return this;
            }

            /// <summary>
            /// Specify that a given <paramref name="action"/> is to be invoked when the current state is exited by the provided <paramref name="trigger"/>.
            /// </summary>
            /// <remarks>This can be called multiple times.</remarks>
            /// <param name="trigger">The trigger that must be fired to invoke the <paramref name="action"/></param>
            /// <param name="action">The action to invoke</param>
            /// <returns>The current <see cref="StateBuilder"/>, allowing for a fluent syntax</returns>
            /// <exception cref="System.ArgumentNullException">
            /// Thrown when <paramref name="trigger"/> or <paramref name="action"/> is null.
            /// </exception>
            public StateBuilder OnExitTo(TTrigger trigger, Action action)
            {
                if (action == null)
                {
                    throw new ArgumentNullException("action");
                }

                return OnExitTo(trigger, x => action());
            }

            /// <summary>
            /// Specify that a given <paramref name="action"/> is to be invoked when the current state is exited by the provided <paramref name="trigger"/>.
            /// </summary>
            /// <remarks>This can be called multiple times.</remarks>
            /// <param name="trigger">The trigger that must be fired to invoke the <paramref name="action"/></param>
            /// <param name="action">The action to invoke</param>
            /// <returns>The current <see cref="StateBuilder"/>, allowing for a fluent syntax</returns>
            /// <exception cref="System.ArgumentNullException">
            /// Thrown when <paramref name="trigger"/> or <paramref name="action"/> is null.
            /// </exception>
            public StateBuilder OnExitTo(TTrigger trigger, Action<StateMachine<TState, TTrigger, TModel>.Transition> action)
            {
                if (trigger == null)
                {
                    throw new ArgumentNullException("trigger");
                }
                else if (action == null)
                {
                    throw new ArgumentNullException("action");
                }

                List<Action<StateMachine<TState, TTrigger, TModel>.Transition>> list;
                if (!_onExitTos.TryGetValue(trigger, out list))
                {
                    _onExitTos[trigger] = list = new List<Action<StateMachine<TState, TTrigger, TModel>.Transition>>();
                }
                list.Add(action);
                
                return this;
            }

            /// <summary>
            /// Specify that this state is a sub-state of the provided <paramref name="superState"/>.
            /// </summary>
            /// <remarks>This can only be called once, but the whole inheritance tree will be followed if it exists.</remarks>
            /// <param name="superState">The super-state that will act as this state's parent.</param>
            /// <returns>The current <see cref="StateBuilder"/>, allowing for a fluent syntax</returns>
            /// <exception cref="System.ArgumentNullException">
            /// Thrown when <paramref name="superState"/> is null.
            /// </exception>
            /// <exception cref="System.ArgumentException">
            /// Thrown when a parenting loop is detected.
            /// </exception>
            /// <exception cref="System.InvalidOperationException">
            /// Thrown when called more than once.
            /// </exception>
            public StateBuilder SubstateOf(TState superState)
            {
                if (superState == null)
                {
                    throw new ArgumentNullException("superState");
                }
                if (_hasSuperState)
                {
                    throw new InvalidOperationException("Cannot set as a Substate more than once.");
                }

                StateBuilder superConfig = _builder.GetConfiguration(superState);
                while (superConfig != null && superConfig._hasSuperState)
                {
                    if (_builder._stateComparer.Equals(_state, superConfig._superState))
                    {
                        throw new ArgumentException(
                            string.Format("Cannot create a parenting loop. State {0} has {1} as a parent already.", superConfig._state, superConfig._superState),
                            "superState");
                    }
                    superConfig = _builder.GetConfiguration(superConfig._superState);
                }

                _hasSuperState = true;
                _superState = superState;
                
                return this;
            }
            
            /// <summary>
            /// Return the <see cref="StateMachineBuilder&lt;TState, TTrigger, TModel&gt;"/> of this StateBuilder, allowing for further configuration.
            /// </summary>
            /// <returns>The <see cref="StateMachineBuilder&lt;TState, TTrigger, TModel&gt;"/></returns>
            public StateMachineBuilder<TState, TTrigger, TModel> End()
            {
                return _builder;
            }

            #endregion

            #region Private methods

            /// <summary>
            /// Convert the provided <paramref name="list"/> to an array.
            /// </summary>
            /// <typeparam name="T">The element type of the list</typeparam>
            /// <param name="list">The list to convert. Assumed to be non-null</param>
            /// <returns>An array that has the same contents of the provided <paramref name="list"/>.</returns>
            private static T[] Freeze<T>(List<T> list)
            {
                if (list.Count == 0)
                {
                    // Enumerable.Empty should return an empty array, but coalesce a new one just in case.
                    return Enumerable.Empty<T>() as T[] ?? new T[0];
                }
                else
                {
                    return list.ToArray();
                }
            }

            /// <summary>
            /// Convert the provided <paramref name="dict"/> to a <see cref="FrozenDictionary&lt;TKey, TValue&gt;"/>.
            /// </summary>
            /// <typeparam name="TKey">The key type of the dictionary</typeparam>
            /// <typeparam name="TValue">The value type of the dictionary</typeparam>
            /// <param name="dict">The dictionary to convert. Assumed to be non-null</param>
            /// <returns>A <see cref="FrozenDictionary&lt;TKey, TValue&gt;"/> that has the same contents and key comparer of the provided <paramref name="dict"/>.</returns>
            private static FrozenDictionary<TKey, TValue> Freeze<TKey, TValue>(Dictionary<TKey, TValue> dict)
            {
                return new FrozenDictionary<TKey, TValue>(dict, dict.Comparer);
            }

            /// <summary>
            /// Convert the provided <paramref name="dict"/> to a <see cref="FrozenDictionary&lt;TKey, TValue&gt;"/>.
            /// </summary>
            /// <typeparam name="TKey">The key type of the dictionary</typeparam>
            /// <typeparam name="TValue">The value type of the dictionary</typeparam>
            /// <param name="dict">The dictionary to convert. Assumed to be non-null</param>
            /// <returns>A <see cref="FrozenDictionary&lt;TKey, TValue&gt;"/> that has the same contents and key comparer of the provided <paramref name="dict"/>.</returns>
            private static FrozenDictionary<TKey, TValue[]> Freeze<TKey, TValue>(Dictionary<TKey, List<TValue>> dict)
            {
                return new FrozenDictionary<TKey, TValue[]>(
                    dict.Select(p => new KeyValuePair<TKey, TValue[]>(p.Key, Freeze(p.Value))),
                    dict.Comparer);
            }

            /// <summary>
            /// Return whether the provided <paramref name="trigger"/> is already configured.
            /// </summary>
            /// <param name="trigger">The trigger to check</param>
            /// <returns>True if the trigger has been previously configured; false otherwise</returns>
            private bool IsTriggerConfigured(TTrigger trigger)
            {
                return _transitions.ContainsKey(trigger)
                    || _dynamicTransitions.ContainsKey(trigger)
                    || _reentries.ContainsKey(trigger)
                    || _ignores.Contains(trigger, _builder._triggerComparer);
            }

            #endregion
        }
    }
}
